fix(search): consolidate dashboard, cross-mode, and composer fixes - #2736
fix(search): consolidate dashboard, cross-mode, and composer fixes#2736BigSimmo wants to merge 38 commits into
Conversation
Clicking into "Find a mode" in the header mode menu drew a sharp accent rectangle around the text while the magnifier icon sat outside it. The input's Tailwind `outline-none` cannot win against the unlayered shared text-field rule in globals.css, which paints an outline on any nested input on focus-visible. Adopt the established fix pair instead of inventing a new override: the container becomes the `search-shell` focus owner and the input carries `search-shell-input`, whose unlayered rule clears the outline and the inset shadow. The Tailwind `focus-within:border-*` utility is dropped because `.search-shell:focus-within` already owns that border shift, so the control keeps a single focus affordance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJ1C29Kazq72SbLHUYZLQX
Production UI shard 1 failed on PR #2638 with a strict-mode violation: `getByTestId("dsm-search-page")` resolved to two elements, the live one under `mobile-composer-reserve-pad` and a hidden streaming twin beside it. The PR's diff was two class names on the header mode-menu search box, which that test never opens, and the same shard passed on the base commit minutes earlier. This is the known `#93` artefact: Next streaming can leave a hidden duplicate page root in the tree under full-suite load. The repository already adjudicated it as a test-side concern and ships `visibleByTestId` for exactly this — its docstring names page-root and shell surfaces as the intended callers, and `tests/dsm-ui-smoke.spec.ts` already scopes the same testid that way. These two call sites in `ui-smoke.spec.ts` were simply never converted, and the file already imports the helper. `expectSingleSettledOwner` is deliberately not used here: the duplicate is hidden and invisible to users, so requiring it to disappear would assert more than the established decision supports. Verified: `npm run test:e2e -- tests/ui-smoke.spec.ts --project=chromium --grep "DSM"` — 2 passed (8.0s); eslint clean on the changed file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJ1C29Kazq72SbLHUYZLQX
Seven expansion patterns listed a long phrase alongside a shorter one that the long phrase always contains, so the shorter alternative matched first and the longer one could never be reached. Two are in Prescribing, one in Tools, and four in Calculators. The trimmed patterns are behaviour-identical: in each pair the surviving phrase is a suffix of the deleted one and carries the same word boundaries, so every string the old pattern matched the new one matches too. The existing unit tests already exercise three of these rules using the long phrasing and still pass unchanged, which is the proof that nothing moved. No rule is added, removed, or retargeted. This deletes dead text only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012j3M3zd5PkGfN5vJe3BiSU
Addresses the Codex review finding on PR #2714. Scoping only the `dsm-search-page` root assertion left every locator after it resolving across both copies, so the same strict-mode violation would reappear one line later as soon as the #93 streaming twin exists: the query ribbon and `dsm-search-result` in the first test, `dsm-category-filter-desktop` in the second, and the diagnosis page root and its h1 after navigation. Each test now owns its visible page root and derives the in-page locators from it, as tests/dsm-ui-smoke.spec.ts already does. Two locators deliberately stay page-scoped, because scoping them is wrong rather than merely unnecessary: `dsm-category-filter-panel` renders through `OverlayPortal` (result-filter-control.tsx), and the "Back to dsm-5" control sits in the in-page nav header, which also portals out of the page root. Both live outside the subtree, so a page-root scope cannot find them — confirmed by the back-link assertion failing under the over-scoped form before this correction. Verified: `npm run test:e2e -- tests/ui-smoke.spec.ts --project=chromium --grep "DSM"` — 2 passed (6.4s); eslint and prettier clean on the file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJ1C29Kazq72SbLHUYZLQX
Immutable review record for the merged sidebar reliability fix: merge commit ee0cf48 tree is identical to branch tip a943909, fix commit 1a6adff is an ancestor of main, no orphaned commits, remote branch already deleted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cm7QVRJrR4gkA7SyN61EoF
…ind it
Four small follow-ups to the "Also matches" disclosure, each independent.
The "Also in your library" tray dropped its match count from assistive tech at
every width. The visible count carries aria-hidden with a comment saying the
button's name should not read it twice, but that tray has no live region to say
it once, and below sm the count is display:none anyway. An sr-only copy now
carries it, so the closed control announces what it opens onto.
On phones the same panel showed "Tap to open", because the cross-mode lookup
waited for the click there. That is the blind door the desktop path was
deliberately built to avoid: the header could not state a count, and an empty
tray was still offered. The lookup is now eager at every width and the no-match
tray is dropped at every width. The cost is one extra cross-mode lookup per
phone search.
The page thumbnails kept the 3px accent top bar that was removed from the
best-match card, so the strip the owner asked to lose simply moved down the
page. Removed, leaving the hairline border.
One inline style={{ maxWidth }} in a design-scratch mockup became max-w-[30ch],
which puts the design-drift ratchet at 232 against its 233 ceiling. It had no
headroom, so the next PR to add a legitimate inline style would have failed on
a limit unrelated to it.
Verified: lint, typecheck, drift ratchet 232/233. Focused browser proof on the
specs the planner attributes to these files; the full Chromium suite is left to
CI, which runs it on this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KYV1sTeo2DHUyJBjzprTRU
…ct cleanup MasterSearchHeader's composer effect cleanup unconditionally stripped `data-composer-reserve` from the page-owned slot. The cleanup does not only run on unmount: any change to the effect's dependencies re-runs it, and the effect body then re-sets the marker about 4ms later. That leaves a window in which the DOM lacks an attribute the page's own client render still produces, and a page segment hydrating inside it logs a hydration mismatch on `data-composer-reserve` (client "pending" vs server null). Observed once on the dictionary catalogue and traced to `commitHookEffectListUnmount` -> `setModeHomeComposerReservePending(..., false)` with the slot not yet ready. The cleanup now applies the same ready-gate adoption already uses: a slot is ours to clear only once its own segment has signalled `data-composer-slot-ready`. Nothing is stranded by skipping. The effect body re-establishes the reserve on the next tick, and a real unmount takes the slot element with it, so `getElementById` returns null there anyway. Search-chrome invariant 15 is untouched: its three required clears (home media query mismatch, suppressed composer, portal adoption fallback) all live in `syncTarget` and the suppression branch, and the second test pins that the suppressed-composer clear still fires on an unhydrated slot. Evidence, same commit, instrumented Chromium on /dictionary/search: without the fix: +620ms REMOVE ready=false (the window) with the fix: no ready=false removal; all clears at ready=true End state identical both ways: reserve absent, slot 80px, composer adopted. Verification: focused unit tests 50 passed / 3 files; tsc --noEmit clean; prettier clean on both files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRRn8uuZosrSaxXSB5J8S5
… now vanish It requires the cross-mode panel to exist at 320px and to sit below the medication results, but it never mocked /api/search/universal. That passed only because the phone lookup was lazy, so the panel rendered a "Tap to open" header whether or not anything was behind it. With the lookup eager the panel correctly drops itself when the query has no cross-mode match, and the ordering assertion went with it. Mocks one documents-domain match for this test only, so the ordering guarantee stays real rather than vacuous. The body is NDJSON, matching the endpoint: a single JSON object parses to no groups and reproduces the same empty panel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KYV1sTeo2DHUyJBjzprTRU
…ained The aggregate went red several times on a UI-only branch while every local gate was green, and CI log access stayed blocked by the babysit marker for the whole session, so the failing job was never read. Recorded with the next action, which is to unlock the marker and read the log before offering a cause. Inbox request only. It applies to the canonical ledger via issues:reconcile after this lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KYV1sTeo2DHUyJBjzprTRU
…retired mode homes came back
Clearing the search box emptied the React query but left `q` and `run=1` in the
URL. `showSharedHome` reads `run=1` straight off the URL, so it stayed suppressed
while the mode branch below it, now seeing no query, fell back to the home its
route had been consolidated away from. Two modes were affected and both were
reproduced in a browser against a running build before the fix:
- `/?mode=prescribing&q=…&run=1`, clear -> `medication-home` (the Dose/Safety/
Monitoring tiles). `tests/ui-tools.spec.ts` asserts in a comment that this view
is "retired and no longer reachable from any route". It was reachable, by that
one click.
- `/documents/search?q=…&run=1`, clear -> `document-search-empty-state`, the
"Start here" home that `documents/page.tsx` records as deliberately retired.
This is the same defect a clinician reported for Sources, in two more modes: the
URL and the composer disagreed, and the mode branch resolved the disagreement by
rendering a page that no longer exists as a destination.
The fix is one handler. `onClearQuery` now also drops the submitted search from
the URL, so the shared home renders. `appModeSelectionHref` rather than
`appModeHomeHref`: every mode reaching this handler is dashboard-owned, so its
home is the shared home at `/`, and `appModeHomeHref("prescribing")` still answers
`/medications`, which only 307s back. `/documents/search` has to leave its own
route entirely, which the selection href also does.
The pre-existing `answer` guard is kept and is load-bearing. A generated answer is
durable state the URL does not own, so the clear button must not discard one; a
browser check confirms answer mode is untouched.
Both paths are pinned by new browser cases, which is where the defect was proved
rather than inferred.
Also fixes the third governance suite that fails on a shallow clone. `#1M0J6D`
added guards to the clinical-hazard and rag-plan suites; `privacy-readiness` was
left without one and still reported `reviewedCommit does not exist: <sha>`, which
reads as a governance breach rather than a missing object. It now mirrors the two
siblings exactly. The gate is not weakened: on a full clone the commit resolves,
`checkGit` stays true, and the real ancestry check runs.
Queues a correction to ledger row `#ZKR5YK`, whose Sources, Documents and
Medication examples are gone now that all three bare paths redirect. Tools is the
only live case left, and it stays an owner ruling rather than a fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QaizDqAQZWHgfGLitWU8xf
…at was removed It asserted the exact `(isWide || modeId === "answer" || expanded)` gate, which was the deferral that produced the phone blind door. Repointed at the eager gate, at the empty-tray guard that now runs at every width, and at the three-way header so the "Tap to open" arm cannot come back unnoticed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KYV1sTeo2DHUyJBjzprTRU
…queued for it Another session had a `done` pending on the same row (4036f064), backed by an implementation of the mode-pill change the row asks for. Two pending mutations on one row block reconciliation for everyone, which is the race `#686WHW` records, and a re-scope is pointless against a close that supersedes the whole row. The findings that prompted the re-scope are verified and worth keeping, so they are recorded in the cancel reason and in this branch's PR rather than lost: the row's Sources, Documents and Medication examples are all gone now that those three bare paths redirect, Favourites is settled by owner decision `#V0EDR4`, and Tools was the only live case left at the time of the check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaizDqAQZWHgfGLitWU8xf
…udget The explanatory comment pushed ClinicalDashboard.tsx to 4141 lines, one over its 4140-line no-growth budget. The gate's advice is to extract a module rather than grow the monolith; extracting this file is a refactor of its own, so the proportionate answer to a one-line overrun is to say the same thing in fewer lines. The full reasoning, the measured reproduction and the mutation check all live in the commit that carried the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaizDqAQZWHgfGLitWU8xf
…ct its alias Tools had two homes. `/tools` is the canonical directory (filter ribbon, full catalogue, detail panel). `/?mode=tools` rendered a separate hub-shaped launcher, because `shouldShowSharedHome` excludes `tools` and so the dashboard had nothing else to fall back to. Which surface a clinician saw depended only on how they arrived: the mode pill gave them the hub, every other link gave them the directory. The hub's one unique feature was its verb shortcut row (Ask, Compare, Prescribe, Safety, Docs, Refer, Forms, Saved), which answers "what am I trying to do" rather than naming a mode. That row moves to `src/components/tools/tool-quick-actions.tsx` and renders above the directory results, sourced from `accessibleTools` so a category filter cannot empty it, and hidden once a query is running. With nothing of its own left to show, `/?mode=tools` now redirects to `/tools` unconditionally, carrying its query string through. `localSmartExcludedToolIds` was declared three times; the shared copy in `src/lib/tools-catalog.ts` is now the one definition. The legacy launcher component is left in place, unreachable, rather than deleted here: `tests/favourites-auth-gate.dom.test.tsx` still proves real guest-access behaviour against it, and re-homing that coverage onto `/tools` is a separate change. Browser cases that loaded `/?mode=tools` now assert the same contracts on `/tools`: the shortcut row, the phone filter sheet, the detail panel launch links, and no shared search chrome on a route that owns its own filtering. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaizDqAQZWHgfGLitWU8xf
… claude/mode-home-clear-fix
The redirect landed clinicians on a directory with no way to type a tools search. `/tools` deliberately renders no shared composer because it owns its own filtering, but its filtering was reachable only through the URL: the box lived on the `/?mode=tools` hub, and redirecting that alias took the box with it. `tests/ui-clinical-ask.spec.ts` caught it — local-only Smart search in tools mode has nowhere to be typed. So the box comes across with the shortcut row, in flow rather than fixed, as a page-owned filter and not a second shared composer. A draft typed here wins over the URL and the shared command draft, the same precedence the hub used. Submitting navigates to `/tools?q=…&run=1` so a result set stays shareable and survives reload, which is what the hub's submit control already did. Verified in a browser on the failing journey: typing a natural-language query hides the shortcut row, submitting reaches the ranked results, Answer, Documents and Favourites stay excluded, and neither the clinical-ask nor the universal-search endpoint is called. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QaizDqAQZWHgfGLitWU8xf
The "Also in your library" line under an answer resolved links from four catalogues loaded in the browser (medications, services, forms, differentials). A clinician asking a question in Answer mode could never be shown a matching DSM diagnosis, dictionary term, formulation, specifier, therapy or tool, though every other mode's "Also matches" tray can reach them through /api/search/universal. Widen the data behind the existing line rather than replacing it. The line keeps its disclosure, its placement inside the answer thread, its walk back through earlier turns, and the per-record "Search in <mode>" action and cross_mode_link_open telemetry that the mode-level tray does not have. - buildCrossModeLinksFromUniversalSearch maps universal-search groups to the same CrossModeLink shape, gated on a query term naming the record at a word boundary, the same weight and minimum term length the differentials path uses. An errored or empty group contributes nothing. - The consumed domains and their complement come from one array, so a domain cannot later be read by both halves and print one record twice. - The lookup is opt-in per surface (universalMode) and off by default, so no surface that already mounts UniversalSearchAlsoMatches runs a second one. - It is withdrawn while a generation is in flight, matching the rule the sibling tray follows on this mode, and it never fires for an answer thread restored onto an unsubmitted shared home. Documents stays excluded: an answer already cites its documents in the evidence rail and the source drawer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Xwq7U1Gu9Ncs2Aj3yFfMF
Ten cases for buildCrossModeLinksFromUniversalSearch, covering the parts that would fail silently: an errored or empty group contributing nothing, a domain the catalogue half already resolves being ignored, a substring hit rejected because the query does not name the record at a word boundary, short query filler dropped, a record the catalogue half already listed not repeated, the per-mode and total caps, and a distinct React key when a domain omits its id. One case pins the two domain lists as exact complements, because that is the property which stops a future domain being read by both halves and printing one record twice. The coverage register's Answer exemption now states the current contract rather than only the 2026-08-26 duplication history, and a new case pins the universalMode opt-in on the answer surface. Without it the exemption's claim that Answer answers cross-mode discovery elsewhere quietly stops being true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Xwq7U1Gu9Ncs2Aj3yFfMF
Review of the first commit found an unstated consequence: the catalogue half caps itself at four links and the universal half added up to two more, so the line's ceiling moved from four rows to six without anyone deciding it. Fill to a combined five instead. The widened reach keeps its full two rows on the common answer, which names no medication, service, form or differential at all, and a thread that already resolved four clinical records grows by one row rather than three. The new browser test is the proof the unit tests cannot give: an answer for a question naming a DSM diagnosis, with only /api/search/universal mocked, must show a Bipolar I Disorder card inside the answer's own library line, with both its open and its search-in-mode controls. It also asserts the request carries exactly the six domains the line consumes, which is what stops a record arriving down both paths, and re-asserts that this surface still has one panel and not two. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Xwq7U1Gu9Ncs2Aj3yFfMF
Section 12.8 records the 2026-08-26 decision to keep one cross-mode panel under an answer and says which one won. It does not say that the winner reached four catalogues while the panel it replaced reached twelve domains, so a reader working from that section would conclude the surfaces were equivalent. They were not, and the gap is invisible from the screenshots the section was written around. Add 12.9: the coverage that was lost, why the fix widens the line rather than undoing the decision, the three constraints that keep it inside 12.8, the new five-link ceiling, and the five modes that remain unreachable because they contribute no cross-entity search domain at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Xwq7U1Gu9Ncs2Aj3yFfMF
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_e7dd1b46-4d96-45ce-8eb6-7cac3580988c) |
Owner decision, 2026-09-07, reversing the collapsed one-line disclosure that direction B introduced on 2026-08-26. The answer surface now renders the same open card strip every other consumer of this block renders, so the same component no longer behaves one way under an answer and another way everywhere else, and the matches are readable without spending a tap. What does not change: the block stays below the governed caution and above the follow-up suggestions, keeps its two per-card actions and their telemetry, and keeps the widened data source added earlier on this branch. CrossModeLinksLine is left in place rather than deleted. It has no consumer now, and it is one prop away from being the presentation again if the owner wants it back. Three browser tests carried the disclosure as their contract. Each is updated rather than dropped, and each now asserts the absence of a trigger alongside a visible rail, so a disclosure reappearing under an answer fails the suite. The 1280px case keeps its computed-display assertion on the rail, because width was where the previous design broke and that class of failure is still worth catching. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Xwq7U1Gu9Ncs2Aj3yFfMF
Section 12.9 explained the widened data source while still describing the collapsed line as the presentation. Record that the line is gone, what reversing it costs and buys, why CrossModeLinksLine stays in the file with no consumer, and how the three browser tests that carried the disclosure were turned into guards against it returning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Xwq7U1Gu9Ncs2Aj3yFfMF
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e3076dd326
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex resolve actionable Codex review findings for this pull request and current head using the repository instructions. This is the pull request's single automatic repair pass: do not perform a fresh review, create new standalone findings, or request another review. Work only the existing unresolved Codex threads on the current head. The only repository destination is BigSimmo/Database, and the only branch destination is the pull request head branch claude/answer-mode-coverage-gap-ilcwui at starting commit e3076dd; never publish fixes to a detached or synthetic work branch and never create a stacked pull request. Use the authenticated GitHub connector to commit each approved fix to BigSimmo/Database:claude/answer-mode-coverage-gap-ilcwui, then verify that the pull request head contains the published commit before reporting success. Always fix P0 and P1 findings. For P2 and lower findings, fix only clear, scoped, low-risk issues; otherwise disposition them with a concise reason. For a fixed thread, reply with as the first line and as the second line. For a no-code disposition, use followed by . These result markers authorize the workflow to close that exact thread only after it verifies a fixed commit is the pull request head; a local-only commit is not a fix. If publication or verification fails, do not use either result marker, do not claim success, and leave the thread open with the blocker. If human input or new authorization is required, do the same. Finish only after every actionable thread is fixed or dispositioned and closed, or explicitly left open for a human decision. Do not update the branch from main, address unrelated reviews, broaden scope, or create more than one scoped fix commit. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation. |
Codex review finding on PR #2736, and it is real. The mapper replaced the API's within-domain ordering with a term-count score, and that score ties constantly: most matches hit exactly one query term. The comparator then fell through to alphabetical title order, so among equally scoring items from one domain the earliest title won. With maxPerMode at 1 that is not a cosmetic reordering, it silently drops the domain's actual top result and shows its second. Carry the item's position in the group the server returned it in and use it as the tiebreaker ahead of the title. Equal score and equal mode priority means the same domain, so that comparison is always within a domain and never an incomparable cross-domain one. item.score is deliberately still unused: its own type says it is comparable within a group only. The new case is the one the reviewer described: a higher-ranked "Zzz" item ahead of a lower-ranked "Aaa" item in the same domain, both matching the same terms. Alphabetical order would take the Aaa record; the domain's ranking takes the Zzz one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Xwq7U1Gu9Ncs2Aj3yFfMF
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Five was set while this block was still a collapsed disclosure, where an extra row cost nothing until the reader opened it. It is open at every width now, so every row is unconditional height on the answer surface, between the governed caution and the follow-ups. Four is the ceiling the catalogue half alone allowed before the widened lookup existed, so holding it keeps this change about reach rather than about size. The cap only binds when the catalogue half filled the strip on its own, and a question naming four medication, service, form or differential records is already well served. The common answer names none of them, which is where the widened reach earns its place and where this number never applies. Renamed the constant: there is no line any more. Also records in the handover why CrossModeLinksLine is still in the file. The owner asked for it to be deleted; check:dead-code-candidate refused, because the symbol was introduced on 2026-08-26 and sits inside the gate's 30-day window. The heuristic is wrong here — its consumer existed and was removed the same day — but tuning a threshold to pass a diff is what that gate exists to stop, so it stays until the window lapses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Xwq7U1Gu9Ncs2Aj3yFfMF
Owner decision, 2026-09-07, after seeing both ends of this built. Neither was right. Closed by default, as direction B shipped it, hides the matches behind a tap nobody has a reason to take: a closed tray cannot show what it holds, so the reader spends the tap to find out whether it was worth spending. Removing the toggle entirely, the first correction, put four unconditional rows into the answer's evidence stack with no way to put them away, on the densest surface in the product. The hybrid is one control that starts open. The matches are readable without a tap and the space is one tap away. variant="line" is the answer surface's presentation again, so the card strip stays what the DSM diagnosis page and the mockups use. The three browser tests that carry this contract now pin the resting state as open and exercise the collapse from there. The 1280px case asserts the rail's own computed display in both states rather than the trigger's word for it: a `hidden` beside a `md:flex` in one class list loses to the media query from 768px up, which once left a rail painted open while its trigger reported it closed. Resting open, the same mechanic would hide a broken collapse instead, so the closed state is the one pinned on computed display. The ceiling stays at four. Its height argument is weaker now the line can be put away, but four is the number the catalogue half alone allowed and there is no reason to spend the difference without asking. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Xwq7U1Gu9Ncs2Aj3yFfMF
Owner decision, 2026-09-07. The header listed the first three record names beside the label from sm up, so the reader saw "Bipolar disorder · East Metropolitan Eating Disorder Specialist Service · Kara Maar Spe…" and then the same records as cards immediately underneath. Those names were doing real work while the line rested closed: a closed tray cannot show what it holds, so the preview was the only way to tell the reader whether opening it was worth a tap. The line rests open now, so the preview repeats what is already on screen and truncates mid-word to do it. The header is label, rule and count at every width, which is the same editorial device the sibling "Also matches" tray uses. The rule no longer stops at sm, because the names are no longer taking its place from there up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Xwq7U1Gu9Ncs2Aj3yFfMF
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_cb631f76-8097-4ccf-bbe3-6c10d873f6fe) |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_b699fd59-5081-423b-bd35-480616e71325) |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_9ff49530-be56-4267-98a6-00790d5594a6) |
…query Editing the bottom composer on a result view calls setQuery alone: the results and modeSearchSubmitted both stay as they were. The lookup was keyed on that live query, so a paused draft fetched cross-mode matches for a search the reader never ran, replaced the tray and its "N related modes" count, and left those matches disagreeing with the primary cards still on screen for the last submitted search. The dashboard now holds the submitted query separately from the draft. Every submission records the text it submitted, clearing the flag clears the query with it, and an auto-run URL seeds it so a restored result view is not blank. Answer mode is unchanged, since it already keyed off the generated answer's query. Tools and Favourites never record a submission and fall through to the draft, which is the only query they have. Prescribing is deliberately left alone. Its medication list is itself debounced against the same draft, so the tray following the draft agrees with the cards above it there rather than contradicting them. tests/universal-also-matches-submitted-query.test.ts pins the derivation, that only the submission wrapper may write the submitted query, and that every submission passes its text. 7 passed, and 113 passed across the 13 dashboard, seed, thread and also-matches suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0142trjgjRAP2GA9wzqoYUAE
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_0315f98f-ce48-4609-9b8d-23c9654bc5e8) |
CI triageCI failed on this PR. Automated classification of the 2 failed job(s):
Compared with main CI run #16436 (failure). Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger. |
Summary
Consolidates dashboard navigation, mode-home clearing, composer sizing, submitted-query ownership, and universal cross-mode coverage. The integration preserves accessible result counts alongside the expanded answer-mode matches.
Consolidates #2736 (f2b26e3), #2727 (2a327b1), #2730 (9f1d4bf), #2725 (a2a539f), #2714 (053891b), #2716 (fef557b), #2718 (d204dce). Original heads are retained as ancestors; absorbed PRs are closed only after publication is verified.
Verification
npm run test -- tests/cross-mode-links.test.ts tests/universal-also-matches-mode-coverage.test.ts tests/universal-also-matches-submitted-query.test.ts tests/master-search-header.dom.test.tsx tests/audit-navigation-auth-regressions.test.ts— 80 passed in 5 files.npm run format— passed; formatting committed.git diff --check— passed.Verification not run: full local PR/release suites; focused integration checks above were selected, and GitHub remains the merge gate. No live provider evaluation, deployment, migration or production data access was performed.
UI verification not run: local browser matrix; applicable hosted UI checks must pass before merge.
Current-base CI limitation: Make the outstanding-issues snapshot merge-safe (v2), and record the measured assessment of two other rows #2735 run 34495136858 failed only the aged ranking fixture provenance test (32 days old). clinical(governance): resolve forms permissions, test guards, and alert tiers (Bundle 1) #2763 carries the existing refresh; these bundles must not be merged past failed required checks.
Follow-up after first hosted CI: extracted submitted-search state and the result-usability helper;
npm run check:maintainability-budgetspasses at 4136/4140 dashboard lines.npm run test -- tests/use-submitted-mode-search.dom.test.tsx tests/universal-also-matches-submitted-query.test.ts tests/clinical-dashboard-helpers.test.ts— 24 passed.Risk and rollout
npm run check:production-readinesson the shared current-base governance integration stopped at six existing privacy release blockers (five pending and one partial). The production-readiness stage did not execute. These approvals remain outstanding.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy)Evidence for those, in the same order: this adds navigation to library records and never a clinical statement, and the governed caution above it is untouched; no patient-identifiable workflow exists in the diff; no Supabase surface is touched;
src/lib/cross-mode-links.tsimportsUniversalSearchGroupfrom@/lib/universal-searchas a type only, erased at build, so the server module's snapshot, rag and supabase chain cannot enter the client bundle; the endpoint's existing demo and live split is unchanged; failure degrades to rendering nothing rather than a guess; and no clinical decision-support behaviour changed.